﻿namespace NJQuery {
	using System;
	using System.Web;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.ComponentModel;
	using System.Collections.Specialized;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to generate a <see href="http://jqueryui.com/demos/button/">jQuery UI Button widget</see>.
	/// </summary>
	[ParseChildren(true), PersistChildren(false), SupportsEventValidation]
	public class Button : ManagedWebControl, IButtonControl, IPostBackEventHandler {


		#region Constants: Default Property Values

		private const bool defaultCausesValidation = true;
		private const bool defaultText = true;

		#endregion


		#region Fields

		private ButtonIcons icons;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Button"/> class.
		/// </summary>
		public Button() : base(HtmlTextWriterTag.A) { } //TODO: We should probably have a RenderStyle property that allows the other acceptable HTML renderings for the button widget to work.

		#endregion


		#region Properties: Widget initialization options

		[DefaultValue(null), MergableProperty(false), PersistenceMode(PersistenceMode.InnerProperty), Category("Appearance")]
		public virtual ButtonIcons Icons {
			get {
				if(icons == null) icons = CreateIcons();
				return icons;
			}
		}

		[DefaultValue(""), Category("Behavior")]
		public virtual string OnClientClick {
			get { return ViewState["OnClientClick"] as string ?? string.Empty; }
			set { ViewState["OnClientClick"] = value; }
		}

		[DefaultValue(defaultText), Category("Appearance")]
		public virtual bool Text {
			get { return ViewState["Text"] as Boolean? ?? defaultText; }
			set { ViewState["Text"] = value; }
		}


		#endregion


		#region Properties: Button Control

		[DefaultValue(""), MergableProperty(false), PersistenceMode(PersistenceMode.InnerProperty), Category("Appearance")]
		public virtual string Label {
			get { return ViewState["Label"] as string ?? string.Empty; }
			set { ViewState["Label"] = value; }
		}

		[DefaultValue(defaultCausesValidation), Themeable(false), Category("Behavior")]
		public virtual bool CausesValidation {
			get { return ViewState["CausesValidation"] as bool? ?? defaultCausesValidation; }
			set { ViewState["CausesValidation"] = value; }
		}

		[DefaultValue(""), Category("Behavior"), Themeable(false)]
		public virtual string ValidationGroup {
			get { return ViewState["ValidationGroup"] as string ?? string.Empty; }
			set { ViewState["ValidationGroup"] = value; }
		}

		[DefaultValue(""), UrlProperty("*.aspx"), Themeable(false), Category("Behavior")]
		public virtual string PostBackUrl {
			get { return ViewState["PostBackUrl"] as string ?? string.Empty; }
			set { ViewState["PostBackUrl"] = value; }
		}

		[DefaultValue(""), Category("Behavior"), Themeable(false)]
		public string CommandName {
			get { return ViewState["CommandName"] as string ?? string.Empty; }
			set { ViewState["CommandName"] = value; }
		}

		[DefaultValue(""), Themeable(false), Category("Behavior"), Bindable(true)]
		public string CommandArgument {
			get { return ViewState["CommandArgument"] as string ?? string.Empty; }
			set { ViewState["CommandArgument"] = value; }
		}

		#endregion


		#region Properties: Protected

		/// <summary>
		/// Gets the event identifier used when posted back in the client's "Click" event.
		/// </summary>
		protected virtual string ClickEventArgIdentifier {
			get {
				// The DefaultButton on the Panel never sends and event arg, so click must be "default"
				return string.Empty;
			}
		}

		#endregion


		#region Events

		[Category("Action")]
		public event EventHandler Click;

		[Category("Action")]
		public event CommandEventHandler Command;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the <see cref="Click"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected virtual void OnClick(EventArgs e) {
			if(Click != null) Click.Invoke(this, e);
		}

		/// <summary>
		/// Raises the <see cref="Command"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected virtual void OnCommand(CommandEventArgs e) {
			if(Command != null) Command.Invoke(this, e);
			RaiseBubbleEvent(this, e);
		}

		#endregion


		#region Methods: Child controls

		/// <summary>
		/// Creates a <see cref="ButtonIcons"/> object to be used as the <see cref="Icons"/> property.
		/// </summary>
		/// <returns>A new <see cref="ButtonIcons"/> instance.</returns>
		protected virtual ButtonIcons CreateIcons() {
			return new ButtonIcons(ViewState);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The <see cref="Manager"/> used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterClientScript(ClientScript.Core);
			manager.RegisterClientScript(ClientScript.UIWidgetFactory);
			manager.RegisterClientScript(ClientScript.UIButtonWidget);

			manager.RegisterClientStyle(ClientStyle.UICore);
			manager.RegisterClientStyle(ClientStyle.UIButtonWidget);
			manager.RegisterTheme();

			manager.RegisterForReadyScript(this);
		}

		/// <summary>
		/// Adds HTML attributes and styles that need to be rendered to the specified <see cref="HtmlTextWriter"/>.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A <see cref="HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
		protected override void AddAttributesToRender(HtmlTextWriter writer) {

			//TODO: Make sure it's an Anchor once the other options are provided
			writer.AddAttribute(HtmlTextWriterAttribute.Href, "#");

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling)
				writer.AddStyleAttribute("display", "none");

			base.AddAttributesToRender(writer);
		}

		protected override void RenderContents(HtmlTextWriter writer) {
			string label = Label;
			if(!string.IsNullOrEmpty(label)) writer.Write(label);
		}

		/// <summary>
		/// Renders the jQuery "Ready Event" script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderReadyScript(ScriptBuilder script) {
			string clientId = ClientID;

			//TODO: Maybe use this as the model for chaining inits ?? Don't know

			script.NewLine();
			script.RenderIdSelector(clientId);

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling) script.Append(".css('display', '')");

			RenderInitScript(script);
			RenderClickEventScript(script);
			script.Append(";");

		}

		/// <summary>
		/// Renders the jQuery UI initialization script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderInitScript(ScriptBuilder script) {

			script.Append(".button(");

			PropertyCollection properties = script.CreateProperties();
			AddInitProperties(properties);
			script.RenderProperties(properties);

			script.Append(")");

		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="properties">A <see cref="PropertyCollection"/> object that will become the jQuery UI initialization options.</param>
		protected virtual void AddInitProperties(PropertyCollection properties) {

			if(!IsEnabled) properties.AddValue("disabled", true);

			if(icons != null && !icons.IsDefault) properties.AddValue("icons", icons);

			bool text = Text;
			if(text != defaultText) properties.AddValue("text", text);

		}

		protected virtual void RenderClickEventScript(ScriptBuilder script) {

			script.Append(".click(function(event, ui) {");

			script.Indent++;
			script.NewLine();

			//Add custom change event
			string onClientClick = OnClientClick ?? string.Empty;
			if(onClientClick.Length != 0) {

				//TODO: Write a regular expression to detect whether it's inline code that needs to be wrapped in a function or simply a function name or function(event, ui) declaration in the future.  That would be so handy.
				script.NewLine();
				script.Append("var click = ");
				script.Append(onClientClick);
				script.Append(";");

				script.NewLine();
				script.Append("if !click(event, ui) return;");

			}

			//TODO: Make all the postbacks work more like this one -- look for __doPostBack(
			script.NewLine();
			script.Append(Page.ClientScript.GetPostBackEventReference(CreateClickPostBackOptions(), true));
			script.Append(";");

			script.NewLine();
			script.Append("return false;");

			script.Indent--;
			script.NewLine();

			script.Append("})");
		}

		protected virtual PostBackOptions CreateClickPostBackOptions() {
			PostBackOptions options = new PostBackOptions(this, ClickEventArgIdentifier);
			options.RequiresJavaScriptProtocol = false;
			string postBackUrl = PostBackUrl;
			if(!string.IsNullOrEmpty(postBackUrl)) options.ActionUrl = HttpUtility.UrlPathEncode(ResolveClientUrl(postBackUrl));
			if(CausesValidation) {
				string validationGroup = ValidationGroup;
				if(Page.GetValidators(validationGroup).Count > 0) {
					options.PerformValidation = true;
					options.ValidationGroup = validationGroup;
				}
			}
			return options;
		}

		#endregion


		#region Methods: Post data/events

		/// <summary>
		/// Enables a server control to process an event raised when a form is posted to the server.
		/// </summary>
		/// <param name="eventArgument">A <see cref="String"/> that represents an optional event argument to be passed to the event handler.</param>
		[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method is amazing.  You get a complaint if you don't include the protected one, but when you do, it tells you to use events.  I included it simply because it gives the derrived control a chance to hook deeper.")]
		protected virtual void RaisePostBackEvent(string eventArgument) {

			Page.ClientScript.ValidateEvent(UniqueID, eventArgument);

			if(eventArgument.Equals(ClickEventArgIdentifier, StringComparison.Ordinal)) {
				if(CausesValidation) Page.Validate(ValidationGroup);
				OnClick(EventArgs.Empty);
				OnCommand(new CommandEventArgs(CommandName, CommandArgument));
			}

		}

		#endregion


		#region Explicit Implementation: IPostBackEventHandler

		void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) {
			RaisePostBackEvent(eventArgument);
		}

		#endregion


		#region Explicit Implementation: IButtonControl

		string IButtonControl.Text {
			get { return Label;  }
			set { Label = value; }
		}

		#endregion


	}

}